﻿using System;
using System.IO;
using System.Net;
using System.Reflection;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Resources;
using System.Linq;
using System.Xml.Linq;

using SBPweb.RIA.Shared.Contracts.Base;
using SBPweb.RIA.Shared.Contracts.Controllers;
using SBPweb.RIA.Shared.Contracts.Events;
using SBPweb.RIA.Shared.Contracts.Models;

using SBPweb.RIA.Client.Contracts.Base;
using SBPweb.RIA.Client.Contracts.Models;

using SBPweb.RIA.Client.Controls.Base;
using SBPweb.RIA.Client.Controls.Models;

namespace SBPweb.RIA.Client.Controls.Controllers
{

    public class PackageLoaderController : ControllerBase<IPackageLoaderModel, PackageLoaderModel, IPackageLoaderController>, IPackageLoaderController
    {

        #region Fields

        #endregion

        #region Constructor logic

        public PackageLoaderController()
        {
            Model = new PackageLoaderModel();
            //if (FormModel != null)
            //{
            //    FormModel.FormChanged += new EventHandler(FormModel_FormChanged);
            //}
        }

        #endregion

        #region Property accessors

        #endregion

        #region Methods

        public static void RegisterApplicationSingleton()
        {
            RegisterController(new PackageLoaderController());
        }

        public void LoadPackage(string packageName)
        {
            LoadFromPackage<IList<Assembly>>(ExtendableApplication.Current.HostBaseUri, packageName, null, new AsyncRequestArgs<IList<Assembly>>());
        }

        public void LoadPackage(string packageName, AsyncRequestHandler<IList<Assembly>> completed)
        {
            LoadFromPackage<IList<Assembly>>(ExtendableApplication.Current.HostBaseUri, packageName, null, new AsyncRequestArgs<IList<Assembly>>(completed));
        }

        public void LoadPackage(string packageName, AsyncRequestArgs<IList<Assembly>> args)
        {
            LoadFromPackage<IList<Assembly>>(ExtendableApplication.Current.HostBaseUri, packageName, null, args);
        }

        public void LoadPackage(Uri baseUri, string packageName)
        {
            LoadFromPackage<IList<Assembly>>(baseUri, packageName, null, new AsyncRequestArgs<IList<Assembly>>());
        }

        public void LoadPackage(Uri baseUri, string packageName, AsyncRequestHandler<IList<Assembly>> completed)
        {
            LoadFromPackage<IList<Assembly>>(baseUri, packageName, null, new AsyncRequestArgs<IList<Assembly>>(completed));
        }

        public void LoadPackage(Uri baseUri, string packageName, AsyncRequestArgs<IList<Assembly>> args)
        {
            LoadFromPackage<IList<Assembly>>(baseUri, packageName, null, args);
        }

        public void LoadFormFromPackage(string packageName, string formPath)
        {
            LoadFromPackage<UIElement>(ExtendableApplication.Current.HostBaseUri, packageName, formPath, new AsyncRequestArgs<UIElement>());
        }

        public void LoadFormFromPackage(string packageName, string formPath, AsyncRequestHandler<UIElement> completed)
        {
            LoadFromPackage<UIElement>(ExtendableApplication.Current.HostBaseUri, packageName, formPath, new AsyncRequestArgs<UIElement>(completed));
        }

        public void LoadFormFromPackage(string packageName, string formPath, AsyncRequestArgs<UIElement> args)
        {
            LoadFromPackage<UIElement>(ExtendableApplication.Current.HostBaseUri, packageName, formPath, args);
        }

        public void LoadFormFromPackage(Uri baseUri, string packageName, string formPath)
        {
            LoadFromPackage<UIElement>(baseUri, packageName, formPath, new AsyncRequestArgs<UIElement>());
        }

        public void LoadFormFromPackage(Uri baseUri, string packageName, string formPath, AsyncRequestHandler<UIElement> completed)
        {
            LoadFromPackage<UIElement>(baseUri, packageName, formPath, new AsyncRequestArgs<UIElement>(completed));
        }

        public void LoadFormFromPackage(Uri baseUri, string packageName, string formPath, AsyncRequestArgs<UIElement> args)
        {
            LoadFromPackage<UIElement>(baseUri, packageName, formPath, args);
        }

        public void LoadFromPackage<T>(string packageName, string componentPath, AsyncRequestHandler<T> completed)
        {
            LoadFromPackage<T>(ExtendableApplication.Current.HostBaseUri, packageName, componentPath, new AsyncRequestArgs<T>(completed));
        }

        public void LoadFromPackage<T>(string packageName, string componentPath, AsyncRequestArgs<T> args)
        {
            LoadFromPackage<T>(ExtendableApplication.Current.HostBaseUri, packageName, componentPath, args);
        }

        public void LoadFromPackage<T>(Uri baseUri, string packageName, string componentPath, AsyncRequestHandler<T> completed)
        {
            LoadFromPackage<T>(baseUri, packageName, componentPath, new AsyncRequestArgs<T>(completed));
        }

        public void LoadFromPackage<T>(Uri baseUri, string packageName, string componentPath, AsyncRequestArgs<T> args)
        {
            string folder = string.Empty;
            int i = -1;
            string uri;
            if (packageName.StartsWith("http"))
            {
                uri = packageName;
            }
            else
            {
                if (!string.IsNullOrEmpty(packageName))
                {
                    packageName = packageName.TrimStart('.');
                    i = packageName.LastIndexOf("/");
                }
                if (i >= 0)
                {
                    folder = packageName.Substring(0, i + 1);
                    packageName = packageName.Substring(i + 1);
                }
                if (!folder.StartsWith("/ClientBin/"))
                {
                    folder = "/ClientBin/" + folder;
                }
                uri = string.Format("{0}{1}{2}", baseUri, folder, packageName);
            }
            if (!packageName.EndsWith(".xap"))
            {
                packageName = string.Format("{0}.xap", packageName);
            }
            if (typeof(T) != typeof(IList<Assembly>))
            {
                args.Arguments.Put<string>("packageName", packageName);
                args.Arguments.Put<string>("componentPath", componentPath);
            }
            args.Arguments.Put<string>("uri", uri);
            if (ExtendableApplication.Current.IsLocalCachePersistencyEnabled)
            {
                Stream s = ExtendableApplication.Current.LocalSiteCache.GetStreamReadOnly(uri);
                if (s != null)
                {
                    RaiseCompleted<T>(args, s);
                    return;
                }
            }

            WebClient client = new WebClient();
            client.OpenReadCompleted += new OpenReadCompletedEventHandler(client_OpenReadCompleted<T>);
            client.OpenReadAsync(new Uri(uri, UriKind.Absolute), args);
        }

        #endregion

        #region Event handlers

        private void client_OpenReadCompleted<T>(object sender, OpenReadCompletedEventArgs e)
        {
            if (e.Error != null)
            {
                throw e.Error;
            }
            AsyncRequestArgs<T> a = e.UserState as AsyncRequestArgs<T>;
            RaiseCompleted<T>(a, e.Result);
            if (ExtendableApplication.Current.IsLocalCachePersistencyEnabled)
            {
                ExtendableApplication.Current.LocalSiteCache.PutStream(a.Arguments.Get<string>("uri"), e.Result);
            }
        }

        private void RaiseCompleted<T>(AsyncRequestArgs<T> userState, Stream stream)
        {
            IList<Assembly> asms = LoadAssembyPackage(stream);

            if (typeof(T) == typeof(IList<Assembly>))
            {
                userState.RaiseCompletedEvent(this, (T)asms);
                return;
            }

            string packageName = userState.Arguments.Get<string>("packageName");
            string componentPath = userState.Arguments.Get<string>("componentPath");
            if (packageName.EndsWith(".xap"))
            {
                packageName = packageName.Replace(".xap", string.Empty);
            }
            Type type = asms.FirstOrDefault().GetType(string.Format("{0}.{1}", packageName, componentPath.Replace("/", ".").Replace(".xaml", string.Empty)));
            if (type != null)
            {
                var component = Activator.CreateInstance(type);
                if (component is UIElement)
                {
                    (this.Model as PackageLoaderModel).SetFormElement(component as UIElement);
                }
                userState.RaiseCompletedEvent(this, (T)component);
            }
        }

        #endregion

        #region Helper functions

        private static IList<Assembly> LoadAssembyPackage(Stream stream)
        {
            string appManifest = new StreamReader(System.Windows.Application.GetResourceStream(
                new StreamResourceInfo(stream, null),
                new Uri("AppManifest.xaml", UriKind.Relative)).Stream).ReadToEnd();

            XElement deploy = XDocument.Parse(appManifest).Root;
            List<XElement> parts = (from assemblyParts in deploy.Elements().Elements()
                                    select assemblyParts).ToList();

            IList<Assembly> asms = new List<Assembly>();

            foreach (XElement xe in parts)
            {
                var xA = xe.Attribute("Source");
                if (xA == null)
                {
                    continue;
                }

                string source = xA.Value;

                AssemblyPart asmPart = new AssemblyPart();

                StreamResourceInfo streamInfo = System.Windows.Application.GetResourceStream(
                    new StreamResourceInfo(stream, "application/binary"),
                    new Uri(source, UriKind.Relative));


                if (streamInfo != null)
                {
                    asms.Add(asmPart.Load(streamInfo.Stream));
                }
                else
                {
                    //TODO: load zips!
                }
            }

            return asms;
        }

        #endregion

    }

}